﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;

namespace Skywave.ComponentModel
{
    /// <summary>
    /// Event handler for storing profile event.
    /// </summary>
    /// <param name="sender">It is the name of object who call that specific event.</param>
    /// <param name="e">It provides functionalities needed for storing a profile to the user on saving and ...</param>
    public delegate void ProfilesHolderProfileEventHandler(object sender, ProfilesHolderProfileEventArgs e);

    /// <summary>
    /// It provides an optimum system to make a profile and save it in a particular file by serializing the objects into memory stream and then serialize the memory stream into the file.
    /// </summary>
    /// <remarks>
    /// This class converts the files into memory stream, so that the changes will not apply directly on the file which can cause speed reduction.
    /// </remarks>
    public class ProfilesHolder
    {
        public const string filenameProfile = "Profile_{0}.profile";
        public const string filenameNames = "Names.bin";
        public const string defaultKey = "Default";
        private Dictionary<int, System.IO.MemoryStream> mProfiles = new Dictionary<int, System.IO.MemoryStream>();
        private Dictionary<string, int> mNames = new Dictionary<string, int>();
        //XmlSerializer does not support IDictionary implemented calsses
        //System.Xml.Serialization.XmlSerializer serializerNames = new System.Xml.Serialization.XmlSerializer(typeof(Dictionary<string, int>));
        BinaryFormatter serializerNames = new BinaryFormatter();
        //XmlSerializer is very low speed
        //System.Xml.Serialization.XmlSerializer serializerProfile = new System.Xml.Serialization.XmlSerializer(typeof(System.IO.MemoryStream));
        static BinaryFormatter serializerProfile = new BinaryFormatter();
        /// <summary>
        /// It is an event which will called when the user wants to save the datas in the profile.
        /// </summary>
        /// <remarks>
        /// It gives a charge of memory stream to the user in order to save those datas in it.
        /// </remarks>
        public event ProfilesHolderProfileEventHandler ProfileStore;
        /// <summary>
        /// It is a constructor which takes the profile folder path as an entry, it can also creates that directory if it is not existed.
        /// </summary>
        /// <param name="storePath">This is a path of the folder in which the profile is stored</param>
        public ProfilesHolder(string storePath)
        {
            if (string.IsNullOrEmpty(storePath))
                return;
            System.IO.MemoryStream ms1;
            System.IO.FileStream fs1;
            string str1;
            int intDefaultId;
            object deserializedObject;

            if (!storePath.EndsWith("\\")) storePath += "\\";
            if (!System.IO.Directory.Exists(storePath)) System.IO.Directory.CreateDirectory(storePath);
            mStorePath = storePath;
            try
            {
                str1 = mStorePath + filenameNames;
                if (System.IO.File.Exists(str1))
                {
                    fs1 = new System.IO.FileStream(str1, System.IO.FileMode.Open);
                    deserializedObject = serializerNames.Deserialize(fs1);
                    if (deserializedObject is Dictionary<string, int>)
                    {
                        mNames = (Dictionary<string, int>)deserializedObject;
                    }
                    else if (deserializedObject is System.Collections.Hashtable) //It's from old ProfileManager, try to convert
                    {
                        mNames.Clear();
                        System.Collections.Hashtable oldNames = (System.Collections.Hashtable)deserializedObject;
                        string profileName;
                        int index1, id1;
                        foreach (string fileName in oldNames.Keys)
                        {
                            profileName = (string)oldNames[fileName];
                            //It is in Profile_#.profile format so try to extract its key
                            index1 = fileName.IndexOf(".");
                            if (index1 > 0)
                            {
                                id1 = int.Parse(fileName.Substring(8, index1 - 8));
                                mNames.Add(profileName, id1);
                            }
                        }
                    }
                    fs1.Close();
                }
            }
            catch (Exception)
            {
            }
            if (!mNames.ContainsKey(defaultKey)) mNames.Add(defaultKey, -1);
            intDefaultId = mNames[defaultKey];
            List<string> toRemoveKeyNames = new List<string>();
            foreach (string key in mNames.Keys)
            {
                if (key != defaultKey)
                {
                    if (mNames[key] == intDefaultId)
                    {
                        mDefault = key;
                    }
                    str1 = storePath + string.Format(filenameProfile, mNames[key]);
                    ms1 = LoadProfileContentFromPhysicalFile(str1);
                    if (ms1 == null)
                        toRemoveKeyNames.Add(key);
                    else
                        mProfiles.Add(mNames[key], ms1);
                    //if (System.IO.File.Exists(str1))
                    //{
                    //    fs1 = new System.IO.FileStream(str1, System.IO.FileMode.Open);
                    //    try
                    //    {
                    //        deserializedObject = serializerProfile.Deserialize(fs1);
                    //        if (deserializedObject is System.IO.MemoryStream)
                    //        {
                    //            ms1 = (System.IO.MemoryStream)deserializedObject;
                    //            fs1.Close();
                    //        }
                    //        else
                    //        {
                    //            //It is an old profile content, so serialize again all of them to a new MemoryStream and use that
                    //            ms1 = new System.IO.MemoryStream();
                    //            bool endOfDeserialize = false;
                    //            while (endOfDeserialize == false)
                    //            {
                    //                serializerProfile.Serialize(ms1, deserializedObject);
                    //                try
                    //                {
                    //                    deserializedObject = serializerProfile.Deserialize(fs1);
                    //                }
                    //                catch (Exception)
                    //                {
                    //                    endOfDeserialize = true;
                    //                }
                    //            }
                    //            //Save the profile again, so that in future we don't need conversion
                    //            fs1.Close();
                    //            fs1 = new System.IO.FileStream(str1, System.IO.FileMode.Create);
                    //            serializerProfile.Serialize(fs1, ms1);
                    //            fs1.Close();
                    //        }
                    //        mProfiles.Add(mNames[key], ms1);
                    //    }
                    //    catch (System.Runtime.Serialization.SerializationException)
                    //    {
                    //        //The profile is old and it may use XmlSerializer and we can't do any work
                    //        //we remove this profile
                    //        fs1.Close();
                    //        toRemoveKeyNames.Add(key);
                    //    }
                    //}
                }
            }
            foreach (string key in toRemoveKeyNames)
            {
                mNames.Remove(key);
            }
        }
        private string mDefault = "";
        /// <summary>
        /// It is basically a name of a profile which is chosen as a presumption.
        /// </summary>
        /// <remarks>
        /// User can set the default profile name optionally when they are more than one.
        /// </remarks>
        public string Default
        {
            get
            {
                return mDefault;
            }
            set
            {
                if (mNames.ContainsKey(value))
                {
                    mDefault = value;
                    mNames[defaultKey] = mNames[mDefault];
                    InternalSaveNames();
                }
            }
        }
        private string mStorePath;
        /// <summary>
        /// It is a "read only" path which was given by the user at the beginning of making a profile.
        /// </summary>
        public string StorePath
        {
            get { return mStorePath; }
        }
        /// <summary>
        /// It supplys user to add the contents in the memory stream to this named profile by the means of calling the "ProfileStore" event.
        /// </summary>
        /// <param name="key">The name of the profile which is chosen by the user</param>
        public bool AddOrSave(string key)
        {
            bool r = false;
            System.IO.MemoryStream newContent = new System.IO.MemoryStream();
            ProfilesHolderProfileEventArgs e = new ProfilesHolderProfileEventArgs(key, newContent);
            int id;
            List<int> IDs;
            string str1;
            System.IO.MemoryStream ms1;

            if (ProfileStore != null)
            {
                ProfileStore(this, e);
                if (!e.IsCanceled)
                {
                    if (!mNames.ContainsKey(key) || !mProfiles.ContainsKey(mNames[key]))
                    {
                        if (!mNames.ContainsKey(key))
                        {
                            IDs = new List<int>();
                            foreach (int value in mNames.Values)
                            {
                                IDs.Add(value);
                            }
                            id = 1;
                            while (IDs.Contains(id)) id++;
                            mNames.Add(key, id);
                        }
                        else
                            id = mNames[key];
                        mProfiles.Add(id, e.Content);
                        InternalSaveNames();
                    }
                    else
                    {
                        id = mNames[key];
                        ms1 = mProfiles[id];
                        mProfiles[id] = e.Content;
                        ms1.Dispose();
                        ms1 = null;
                    }
                    str1 = mStorePath + string.Format(filenameProfile, id);
                    System.IO.FileStream fsProfile = new System.IO.FileStream(str1, System.IO.FileMode.Create);
                    serializerProfile.Serialize(fsProfile, e.Content);
                    fsProfile.Close();
                    r = true;
                }
            }
            //
            return r;
        }
        /// <summary>
        /// It opens the specific file by using the given profile name and reads that memory stream and returns it to the user. 
        /// </summary>
        /// <remarks>
        /// It is usually used to deserialize the opbjects in the memory stream.
        /// </remarks>
        /// <param name="key">The name of the profile which is going to be deserialized.</param>
        /// <returns>It returns that particular memory stream</returns>
        public System.IO.MemoryStream Load(string key)
        {
            System.IO.MemoryStream resualt = null;
            if (mNames.ContainsKey(key) && mProfiles.ContainsKey(mNames[key]))
            {
                resualt = mProfiles[mNames[key]];
                resualt.Position = 0;
            }
            return resualt;
        }
        /// <summary>
        /// It deletes the given profile name and the containing folder.
        /// </summary>
        /// <param name="key">The name of the profile name which is going to be deleted.</param>
        public void Delete(string key)
        {
            int id;
            System.IO.MemoryStream ms1;
            string str1;

            if (mNames.ContainsKey(key))
            {
                id = mNames[key];
                str1 = mStorePath + string.Format(filenameProfile, id);
                if (System.IO.File.Exists(str1))
                {
                    try
                    {
                        System.IO.File.Delete(str1);
                    }
                    catch (Exception)
                    {
                        return;
                    }
                }
                if (mProfiles.ContainsKey(id))
                {
                    ms1 = mProfiles[id];
                    mProfiles.Remove(id);
                    ms1.Dispose();
                    ms1 = null;
                }
                mNames.Remove(key);
                InternalSaveNames();
            }
        }
        /// <summary>
        /// Shows the list of the all profiles which was made by the user.
        /// </summary>
        /// <returns>Returns the list of the profile names.</returns>
        public List<string> GetProfileNames()
        {
            List<string> resault = new List<string>();
            foreach (string key in mNames.Keys)
            {
                if (key != defaultKey)
                    resault.Add(key);
            }
            return resault;
        }
        /// <summary>
        /// Saves all of the profile names in a special file.
        /// </summary>
        private void InternalSaveNames()
        {
            System.IO.FileStream fsNames = new System.IO.FileStream(mStorePath + filenameNames, System.IO.FileMode.Create);
            serializerNames.Serialize(fsNames, mNames);
            fsNames.Close();
        }

        /// <summary>
        /// Return physical storage file path of provided profileName.
        /// </summary>
        public string GetProfileFilePath(string profileName)
        {
            if (mNames.ContainsKey(profileName))
                return mStorePath + string.Format(filenameProfile, mNames[profileName]);
            else
                return null;
        }

        /// <summary>
        /// Load saved memory stream of provided Profile physical file path.
        /// </summary>
        public static System.IO.MemoryStream LoadProfileContentFromPhysicalFile(string profilePhysicalFilePath)
        {
            System.IO.FileStream fs1;
            System.IO.MemoryStream ms1 = null;
            object deserializedObject;
            if (System.IO.File.Exists(profilePhysicalFilePath))
            {
                fs1 = new System.IO.FileStream(profilePhysicalFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                try
                {
                    deserializedObject = serializerProfile.Deserialize(fs1);
                    if (deserializedObject is System.IO.MemoryStream)
                    {
                        ms1 = (System.IO.MemoryStream)deserializedObject;
                        fs1.Close();
                        fs1.Dispose();
                    }
                    else
                    {
                        //It is an old profile content, so serialize again all of them to a new MemoryStream and use that
                        ms1 = new System.IO.MemoryStream();
                        bool endOfDeserialize = false;
                        while (endOfDeserialize == false)
                        {
                            serializerProfile.Serialize(ms1, deserializedObject);
                            try
                            {
                                deserializedObject = serializerProfile.Deserialize(fs1);
                            }
                            catch (Exception)
                            {
                                endOfDeserialize = true;
                            }
                        }
                        //Save the profile again, so that in future we don't need conversion
                        fs1.Close();
                        fs1.Dispose();
                        fs1 = new System.IO.FileStream(profilePhysicalFilePath, System.IO.FileMode.Create);
                        serializerProfile.Serialize(fs1, ms1);
                        fs1.Close();
                        fs1.Dispose();
                    }
                }
                catch (System.Runtime.Serialization.SerializationException)
                {
                    //The profile is old and it may use XmlSerializer and we can't do any work
                    //we remove this profile
                    fs1.Close();
                    fs1.Dispose();
                    if (ms1 != null)
                        ms1.Dispose();
                    ms1 = null;
                }
            }
            if (ms1 != null)
                ms1.Position = 0;
            return ms1;
        }
    }
}
